#include<iostream>
#include<string>
#include<cstdlib>
#include<unistd.h>
#include<signal.h>
#include<string.h>
#include<sys/types.h>
using namespace std;
//学习一些信号的剩余的知识点：

//int flag =0;
volatile int flag=0;//为了防止内存不可见的问题出现，可以在变量前面加上volatile关键字，强制让编译器每次去内存中访问变量，不要优化到寄存器中
void handler(int signo)
{
    cout<<"i get a signo :"<<signo<<endl;
    flag=1;
}

//补充学习：volatile关键字，作用是强制CPU每次使用该变量的时候都要去内存中访问，用来防止编译器过于优化代码
//优化条件下，某个变量可能会被优化到CPU自己的寄存器中，从而不需要进行访存，提高执行效率
void test1()
{
    signal(2,handler);//发送2号信号就会把flag的值置1，此时循环应该终止
    while(!flag)//在优化条件下，由于flag后续并没有使用，只是作为一个判断真值的变量，所以此时CPU可能会优化我们的flag到自己的寄存器中，并且记录的值是0，所以此时执行while循环的判断条件时，会直接使用寄存器中存储的0，而不是去内存中访问这个值，导致了内存不可见的问题，此时我们发送2号信号修改flag为1，但是CPU只用自己寄存器的值是0，导致死循环
    {
        cout<<"process quit normal"<<endl;
       
    }

}


//打印信号未决位图：
void PrintPending(sigset_t &pending)//sigset_t 是一种信号集（本质是位图），对于这种数据类型的变量我们不能直接操作它的值，应该使用系统接口去改变
{
    for(int signo=31;signo>=1;signo--)//由于0号信号是不存在的，所以我们直接从1号信号开始判断
    {
        if(sigismember(&pending,signo))//sigismember函数用来判断一个信号是否是该信号集的成员，信号集中对应的bit位为1就是该信号集的成员
        {               //如果signo号信号是未决信号集中的成员就会返回1
            cout<<"1";
        }
        else {cout<<"0";}
    }
    cout<<endl;
}



//注意：信号的pending位图中由1->0的时机：是先清0再执行信号对应的行为的

void myhander(int signo)
{
    int ct=0;
    while(true)//我们延长信号的处理时间，此时在发送信号看该信号是否真的被阻塞了，也就是检查未决位图一直置为1
    {
        sigset_t set;
        sigpending(&set);//获取当前进程的未决信号集
        PrintPending(set);//打印未决信号集
        sleep(1);
        if(ct==10)
        {
            cout<<ct<<"i get a signo:"<<signo<<endl;
            break;
        }
        ct++;
    }
}


//补充：信号被处理的时候，该信号会被添加到block表中，防止信号捕捉被嵌套调用，也就是同一时刻收到的多条同个信号只会执行一次，因为在处理一个信号的时候这个信号会被阻塞，直到处理完毕才会恢复，而在处理的时候，如果还有该信号到来，就会在未决态保存着，直到阻塞恢复再执行
void test2()
{
    signal(2,myhander);
    while(true)
    {
        sigset_t set;
        sigpending(&set);//获取当前进程的未决信号集
        PrintPending(set);//打印未决信号集
        sleep(1);
        
    }

}
//我们观察到的现象：当我们发送2号信号后，程序开始执行2号信号的执行，但是我们此时再发送2号信号，此时未决信号集中的2号信号为1，说明此时2号信号被阻塞了，当我们执行完一次2号信号后，此时2号信号恢复为未阻塞，但是此时未决信号集中保存了一个号信号，此时立即清空未决集中的2号信号并且立刻执行2号信号的行为
//一切都符合我们的预期，说明信号在执行的时候自己的信号会被阻塞，执行完再恢复，如果此时还有该信号产生，就会保存在未决信号集中


void handler2(int signo)
{
    cout<<"i get a signo:"<<signo<<endl;
    int ct=20;
    while(ct)
    {
        sigset_t set;
        sigpending(&set);//获取当前进程的未决信号集
        PrintPending(set);//打印未决信号集
        sleep(1);
        ct--;
    }
    
}

//扩展：在执行信号的时候该信号会被阻塞，其实还可以设置在执行某个信号的时候，阻塞多个信号，也就是执行A信号的时候，自动阻塞A，B，C...信号，执行完再恢复
//使用sigaction函数，也要使用sigaction结构体
void test3()
{
    struct sigaction act,oact;//创建两个对象
    //sigaction结构体中有多个成员，我们主要了解其中的sa_mask成员：
    //sigset_t sa_mask保存在信号处理期间要被阻塞的信号集

    //先清空：对于结构体需要使用memset：
    memset(&act,0,sizeof(act));
    memset(&oact,0,sizeof(oact));

    //对于sigset_t 类型可以使用对应的接口清空：
    sigemptyset(&act.sa_mask);

    //设置在执行信号时的屏蔽集：不用设置自己因为自己默认被屏蔽
    sigaddset(&act.sa_mask,1);
    sigaddset(&act.sa_mask,3);
    sigaddset(&act.sa_mask,4);
    sigaddset(&act.sa_mask,5);
    
    //设置执行方法，也就是signal函数的本质也是改变信号的sigaction结构体中的执行方法成员sa_handler
    act.sa_handler=handler2;//也可以设置为SIG_IGN SIG_DFL

    sigaction(2,&act,&oact);//sigaction函数，指定要处理的信号，然后把新的执行时屏蔽信号集和新执行方法的sigaction结构体设置进去，然后把旧的输出到oact中
    //此时我们就设定好了，在2号信号执行的时候要屏蔽（阻塞）的信号
    //注意：此时我们在新的信号屏蔽集中并没有设置2号信号，这会导致2号信号执行时不会阻塞2号信号，会导致信号的嵌套使用，不想发生这个情况就需要显式的设置2号信号到执行时的屏蔽信号集

    while(true)
    {
        cout<<"i am a process:"<<getpid()<<endl;
        sleep(1);
    }
  //测试方法：在执行程序后，给一个2号信号，此时执行2号信号，此时我们给1,3,4,5号信号，看是不是在未决信号集中

  //测试结果：符合预期

}

//当有多个信号未决的时候，先执行哪个信号：
//补充：信号有不同的默认优先级，这些优先级决定了信号被处理的顺序。具有较高优先级的信号会先于优先级较低的信号被处理。例如，SIGKILL 和 SIGSTOP 是两个不能被用户空间忽略或捕获的信号，它们具有最高的优先级。
//优先级相同时就会考虑它们的产生时间等因素最后决定它们的执行顺序



//扩展：用户级页表和进程数量一样，进程具有独立性，但是内核页表只有一份
//当我们的代码使用系统调用等原因要使用内核权限的代码的时候，我们的进程会陷入内核态，处理完之后又会返回用户态，所以我们的程序在执行的时候，进程一直在用户和内核态之间切换




//扩展2：可重入函数：如果一个函数在被重复进入的情况下，出错了，或者可能出错，那就好不可重入函数，否则就是可重入函数
//也就是当有多个执行流对同一个目标执行到了同一个函数，这样会导致出现一定的错误，例如两个执行流来对一个链表进行插入，当A在插入一部分的时候，B又在该位置插入了结点，导致结点丢失，内存泄漏等一系列问题
//我们学过的大部分函数都是不可重入的函数
int main()
{

    test3();
    return 0;
}


